#include <types.h>
#include <queue.h>
#include <list.h>
#include <conio.h>
#include <string.h>

#include <schedule.h>
#include <sys/kmalloc.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/unistd.h>

/* forward declarations */
static ipc_t *ipc_find(const char *name);
static void ipc_dealloc(ipc_t * obj);

static list_t ipc_objlist;

int ipc_create(const char *name, ipc_t ** objp)
{
    ipc_t *obj = NULL;
    int error = 0;
    char str[MAXOBJ_NAME];

    if (!name)
	str[0] = '\0';
    else {
	error = copyinstr(name, str, MAXOBJ_NAME);
	if (error < 0)
	    return (error);
    }

    sched_lock();
    if (curtask->nobjects > MAXOBJ_NUM) {
	sched_unlock();

	return NULL;
    }

    if (copyout(&obj, objp, sizeof(ipc_t))) {
	sched_unlock();

	return -1;
    }

    obj = ipc_find(str);
    if (obj) {
	sched_unlock();

	return -3;		/* object exists! */
    }

    obj = (ipc_t *) kmalloc(sizeof(ipc_t), GFP_KERNEL);
    if (!obj) {
	obj = NULL;

	return -2;
    }

    if (name)
	strncpy(obj->name, str, MAXOBJ_NAME);

    obj->owner = curtask;
    queue_init(&obj->recvq);
    queue_init(&obj->sendq);
    list_insert(&curtask->ipc_objs, &obj->task_link);
    curtask->nobjects++;
    list_insert(&ipc_objlist, &obj->link);
    copyout(&obj, objp, sizeof(ipc_t));

    sched_unlock();

    return 0;
}

int ipc_lookup(const char *name, ipc_t ** objp)
{
    ipc_t *obj = NULL;
    char str[MAXOBJ_NAME];
    int error = 0;

    error = copyinstr(name, str, MAXOBJ_NAME);
    if (error < 0) {
	return (error);
    }

    sched_lock();
    obj = ipc_find(str);
    if (!obj) {
	sched_unlock();

	return -10;
    }
    sched_unlock();

    if (copyout(&obj, objp, sizeof(ipc_t))) {
	return -11;
    }

    return 0;
}

/* for internal use with ipc_lookup() */
static ipc_t *ipc_find(const char *name)
{
    ipc_t *obj = NULL;
    list_t *entry = NULL;

    for (entry = list_first(&ipc_objlist); entry != &ipc_objlist;
	 entry = list_next(entry)) {
	obj = list_entry(entry, ipc_t, link);
	if (!strncmp(obj->name, name, MAXOBJ_NAME)) {
	    break;
	}
    };

    return (obj);
}

int ipc_invalid(ipc_t * obj)
{
    ipc_t *tmp = NULL;
    list_t *entry = NULL;

    for (entry = list_first(&ipc_objlist); entry != &ipc_objlist;
	 entry = list_next(entry)) {
	tmp = list_entry(entry, ipc_t, link);
	if (tmp == obj)
	    return 1;
    };

    return 0;
}

/* internal version of ipc_destroy() */
static void ipc_dealloc(ipc_t * obj)
{
    msg_abort(obj);
    obj->owner->nobjects--;
    list_remove(&obj->task_link);
    list_remove(&obj->link);
    kfree(obj);
}

int ipc_destroy(ipc_t * obj)
{
    sched_lock();
    if (!ipc_invalid(obj)) {
	sched_unlock();

	return -1;
    }
    /* check owner of ipc object */
    if (obj->owner != curtask) {
	sched_unlock();

	return -2;
    }

    ipc_dealloc(obj);
    sched_unlock();

    return 0;
}

void ipc_cleanup(task_t * task)
{
    ipc_t *obj = NULL;

    while (!list_empty(&task->ipc_objs)) {
	obj = list_entry(list_first(&task->ipc_objs), ipc_t, task_link);
	ipc_dealloc(obj);
    };
}

void ipc_init(void)
{
    list_init(&ipc_objlist);
}
